package com.gitee.urick.union.model;

import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.ast.util.HeadingCollectingVisitor;
import com.vladsch.flexmark.ext.tables.TableCell;
import com.vladsch.flexmark.ext.tables.TablesExtension;
import com.vladsch.flexmark.ext.tables.internal.TableNodeRenderer;
import com.vladsch.flexmark.ext.toc.TocBlock;
import com.vladsch.flexmark.ext.toc.TocExtension;
import com.vladsch.flexmark.ext.toc.TocUtils;
import com.vladsch.flexmark.ext.toc.internal.TocNodeRenderer;
import com.vladsch.flexmark.ext.toc.internal.TocOptions;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.html.renderer.DelegatingNodeRendererFactory;
import com.vladsch.flexmark.html.renderer.NodeRenderer;
import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.data.DataHolder;
import com.vladsch.flexmark.util.data.DataKey;
import com.vladsch.flexmark.util.data.MutableDataHolder;
import com.vladsch.flexmark.util.data.MutableDataSet;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Rick 2021/02/08
 * @since 1.0
 */
public class HtmlParser {
    private final List<HtmlToc> tocList = new ArrayList<>();
    private final DataHolder options;
    private final Parser parser;
    private final HtmlRenderer renderer;


    public HtmlParser(boolean toc) {
        if (toc) {
            options = new MutableDataSet().set(Parser.EXTENSIONS, Arrays.asList(TocExtension.create(), new CustomTocExtension()));
        } else {
            options = new MutableDataSet().set(Parser.EXTENSIONS, Arrays.asList(TablesExtension.create(),new CustomTableExtension()))
                    .set(TablesExtension.CLASS_NAME, "tableblock frame-all grid-all spread");
        }
        parser = Parser.builder(options).build();
        renderer = HtmlRenderer.builder(options).indentSize(2).build();
    }

    /*----------------TOC Custom Start-------------------*/
    class CustomTocFactory implements DelegatingNodeRendererFactory {
        @Override
        public NodeRenderer apply(DataHolder options) {
            return new CustomTocNodeRenderer();
        }

        @Override
        public Set<Class<?>> getDelegates() {
            Set<Class<?>> set = new HashSet<>();
            set.add(TocNodeRenderer.Factory.class);
            return set;
        }
    }

    class CustomTocNodeRenderer implements NodeRenderer {

        @Override
        public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
            TocOptions tocOptions = new TocOptions(options, false);
            Set<NodeRenderingHandler<?>> set = new HashSet<>();
            set.add(new NodeRenderingHandler<>(Heading.class, (node, context, html) -> {
                if (context.getHtmlOptions().renderHeaderId) {
                    //                    String id = context.getNodeId(node);
                    //                    if (id != null) {
                    html.attr("id", node.getAnchorRefText());
                    //                    }
                }

                if (context.getHtmlOptions().sourcePositionParagraphLines) {
                    html.srcPos(node.getChars()).withAttr().tagLine("h" + node.getLevel(), () -> {
                        html.srcPos(node.getText()).withAttr().tag("span");
                        context.renderChildren(node);
                        html.tag("/span");
                    });
                } else {
                    html.srcPos(node.getText()).withAttr().tagLine("h" + node.getLevel(), () -> context.renderChildren(node));
                }
            }));
            set.add(new NodeRenderingHandler<>(TocBlock.class, (node, context, html) -> {
                HeadingCollectingVisitor visitor = new HeadingCollectingVisitor();
                List<Heading> headings = visitor.collectAndGetHeadings(context.getDocument());
                List<HtmlToc> filterToc = TocUtils.filteredHeadings(headings, tocOptions).stream().map(h -> new HtmlToc(h.getAnchorRefText(), h.getLevel())).collect(Collectors.toList());
                HtmlToc tempToc = null;
                int lastLevel = 0, pareLevel = 0;
                int tocOrder = 0, childTocOrder = 0;
                boolean childFlag = false;

                for (HtmlToc t : filterToc) {
                    if (lastLevel == 0) {//第一次遍历
                        lastLevel = t.getLevel();
                    }
                    if (childFlag || lastLevel < t.getLevel()) {//存在子节点
                        if (pareLevel == t.getLevel()) {//跳出子节点遍历
                            childFlag = false;
                            childTocOrder = 0;
                            t.setOrder(++tocOrder);
                            tempToc = t;
                            tocList.add(t);
                        } else {
                            childFlag = true;
                            t.setOrder(++childTocOrder);
                            pareLevel = tempToc.getLevel();
                            tempToc.addChildren(t);
                        }
                    } else {//不存在子节点
                        childFlag = false;
                        t.setOrder(++tocOrder);
                        tempToc = t;
                        tocList.add(t);
                    }
                    lastLevel = t.getLevel();
                }
            }));
            return set;
        }
    }

    class CustomTocExtension implements HtmlRenderer.HtmlRendererExtension {
        @Override
        public void rendererOptions(MutableDataHolder options) {

        }

        @Override
        public void extend(HtmlRenderer.Builder htmlRendererBuilder, String rendererType) {
            htmlRendererBuilder.nodeRendererFactory(new CustomTocFactory());
        }

    }

    /*----------------TOC Custom End-------------------*/

    /*----------------Table Custom Start-------------------*/

    class CustomTableFactory implements DelegatingNodeRendererFactory {
        @Override
        public NodeRenderer apply(DataHolder options) {
            return new CustomTableNodeRenderer();
        }

        @Override
        public Set<Class<?>> getDelegates() {
            Set<Class<?>> set = new HashSet<>();
            set.add(TableNodeRenderer.Factory.class);
            return set;
        }
    }

    class CustomTableNodeRenderer implements NodeRenderer {
        @Override
        public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
            Boolean columnSpans = TablesExtension.COLUMN_SPANS.get(options);
            Set<NodeRenderingHandler<?>> set = new HashSet<>();
            set.add(new NodeRenderingHandler<>(TableCell.class, (node, context, html) -> {
                html.attr("class","tableblock halign-left valign-top");
                String tag = node.isHeader() ? "th" : "td";
                if (node.getAlignment() != null) {
                    html.attr("align", getAlignValue(node.getAlignment()));
                }

                if (columnSpans && node.getSpan() > 1) {
                    html.attr("colspan", String.valueOf(node.getSpan()));
                }
                html.srcPos(node.getText()).withAttr().tag(tag);
                context.renderChildren(node);
                html.tag("/" + tag);
            }));
            return set;
        }

        private String getAlignValue(TableCell.Alignment alignment) {
            switch (alignment) {
                case LEFT:
                    return "left";
                case CENTER:
                    return "center";
                case RIGHT:
                    return "right";
            }
            throw new IllegalStateException("Unknown alignment: " + alignment);
        }
    }

    class CustomTableExtension implements HtmlRenderer.HtmlRendererExtension {
        @Override
        public void rendererOptions(MutableDataHolder options) {
        }

        @Override
        public void extend(HtmlRenderer.Builder htmlRendererBuilder, String rendererType) {
            htmlRendererBuilder.nodeRendererFactory(new CustomTableFactory());
        }
    }

    /*----------------Table Custom End-------------------*/

    public String parserHead(String markdown) {
        Document document = parser.parse("[TOC] \n " + markdown);
        return renderer.render(document);
    }

    public String parserHtml(String markdown) {
        Document document = parser.parse(markdown);
        return renderer.render(document);
    }

    public List<HtmlToc> getTocList() {
        return tocList;
    }

    public static class HtmlToc {
        private String name;
        private Integer level;
        private Integer order;
        private List<HtmlToc> children;

        public HtmlToc(String name, Integer level) {
            this.name = name;
            this.level = level;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Integer getLevel() {
            return level;
        }

        public void setLevel(Integer level) {
            this.level = level;
        }

        public List<HtmlToc> getChildren() {
            return children;
        }

        public void setChildren(List<HtmlToc> children) {
            this.children = children;
        }

        public void addChildren(HtmlToc child) {
            if (Objects.isNull(this.children)) this.children = new ArrayList<>();
            this.children.add(child);
        }

        public Integer getOrder() {
            return order;
        }

        public void setOrder(Integer order) {
            this.order = order;
        }

        @Override
        public String toString() {
            return this.level + "-" + this.name;
        }
    }
}
